// h2socket.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "h2socket.h"
#include "h2log.h"

#pragma comment(lib, "ws2_32.lib")


// ============================================================================
// Construction of h2socket class
// ============================================================================
h2socket::h2socket()
{
	_socket = INVALID_SOCKET;
	_port = 0;
	_ip = 0;

	set_buffer_size(H2SOCKET_SEND_BUFF_SIZE, H2SOCKET_RECV_BUFF_SIZE);
}


// ============================================================================
// Destruction of h2socket class
// ============================================================================
h2socket::~h2socket()
{
    disconnect();
}


// ============================================================================
// Init winsock by calling WSAStartup()
// ============================================================================
int h2socket::init_winsock()
// return: <0> succeed, <-1> failed
{
	WSADATA wsaData;
	int iResult;
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (NO_ERROR != iResult)
	{
        LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Error WSAStartup: %d", iResult);
        return -1;
    }

    LOG::debug(DEBUG_SOCKET | DEBUG_L2, __FUNCTION__"() WSAStartup initialized: %d", iResult);
	return 0;
}


// ============================================================================
// Release winsock by calling WSACleanup()
// ============================================================================
void h2socket::release_winsock()
{
	WSACleanup();
    LOG::debug(DEBUG_SOCKET | DEBUG_L4, __FUNCTION__"() WSACleanup.");
}


// ============================================================================
// Allocate memory for send&recv buffer
// ============================================================================
int h2socket::set_buffer_size(int set_buffer_size, int recv_buffer_size)
// [set_buffer_size]: maximum size of send buffer, recommended value = 65535
// [recv_buffer_size]: maximum size of recv buffer, recommended value = 65535
// return: <0> succeed, <-1> failed
{
    _send_buff.resize(set_buffer_size);
    _recv_buff.resize(recv_buffer_size);
    *_send_buff.tail() = 0;
    *_recv_buff.tail() = 0;

    LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() send buffer size:%d/%d, recv buffer size:%d/%d", 
		_send_buff.length(), _send_buff.room(), _recv_buff.length(), _recv_buff.room());
	return 0;
}


// ============================================================================
// Set synchronize mode as Block or Asynchronize
// ============================================================================
void h2socket::set_sync_mode(bool bAsync)
// [iMode]: <H2SOCKET_BLOCK_MODE:0> Block Mode, <H2SOCKET_ASYNC_MODE:1> Asynchronize Mode
{
    u_long iMode = (bAsync ? 1 : 0);

	if(INVALID_SOCKET != _socket)
	{
		ioctlsocket(_socket, FIONBIO, &iMode);
		LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() Connect socket set sync mode %d. (SOCKET=%d)", iMode, _socket);
	}
}




// ============================================================================
// Attach existing socket handle as connect socket
// ============================================================================
bool h2socket::attach_socket(SOCKET sock)
// [hConnSocket]: SOCKET handle
// return: <0> succeed, <-1> failed
{
	if(INVALID_SOCKET != _socket)
	{
		disconnect();
	}
    
	_socket = sock;
	return true;
}


// ============================================================================
// Set connect IP address
// ============================================================================
void h2socket::set_serv_host(const char *pszhost)
// [pstrIP]: IP string, e.g. "127.0.0.1"
{
    LPHOSTENT phostent = gethostbyname(pszhost);
    if(phostent)
    {
        _ip = *(u_long *) phostent->h_addr_list [0];
    }
    else
    {
	    _ip = inet_addr(pszhost);
    }
}



// ============================================================================
// Connect to IP & port specified by set_serv_host() & set_conn_port()
// ============================================================================
bool h2socket::connect()
// return: <0> succeed, <-1> failed
{
	_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (_socket == INVALID_SOCKET)
	{
		LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Error calling socket(). (errorcode=%ld)", WSAGetLastError());
	    return false;
	}

	sockaddr_in clientService; 
	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = _ip;
	clientService.sin_port = htons(_port);

	if (SOCKET_ERROR == ::connect(_socket, (SOCKADDR*) &clientService, sizeof(clientService)))
	{
		LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Error calling connect(). (SOCKET=%d, IP=%d.%d.%d.%d PORT=%d)", _socket,
			_ip&0xFF, (_ip>>8)&0xFF, (_ip>>16)&0xFF, (_ip>>24)&0xFF, _port);
		closesocket(_socket);
		_socket = INVALID_SOCKET;
		return false;
	}

    _recv_buff.clear();
    _send_buff.clear();
    *_recv_buff.tail() = 0;
    *_send_buff.tail() = 0;
	LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() Connected to server. (SOCKET=%d, IP=%d.%d.%d.%d PORT=%d)", _socket,
		_ip&0xFF, (_ip>>8)&0xFF, (_ip>>16)&0xFF, (_ip>>24)&0xFF, _port);

	return true;
}


// ============================================================================
// Close connect socket
// ============================================================================
bool h2socket::disconnect()
// return: <0> succeed, <-1> failed
{
	::closesocket(_socket);
	_socket = INVALID_SOCKET;
	LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() Connect socket %d closed.", _socket);
	return true;
}


// ============================================================================
// Send data to connect socket
// ============================================================================
int	h2socket::send(const char* pstrSend, int iSize)
// [pstrSend]: data to be sent
// [iSize]: size of data
// return: <0> succeed, <-1> failed
{
	if(!connection_ready())
    {
        // try to reconnect to server
        if(!connect() && !connection_ready(1000))
        {
		    LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() Socket not connected.");
		    return -1;
        }
    }

	// send buffer first 
	flush_buffer();

	// buffer is full
    _send_buff.make_room(iSize);
    if(_send_buff.room() < iSize)
	{
		LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Send buffer overflow. (%d+%d>%d)", 
            iSize, _send_buff.length(), _send_buff.room());
		return -1;
	}

    // copy data to pre-buffer
    memcpy(_send_buff.tail(), pstrSend, iSize);
    _send_buff.extend(iSize);
    *_send_buff.tail() = 0;

	return flush_buffer();
}


// ============================================================================
// Send data to connect socket
// ============================================================================
int	h2socket::send(packet* msg)
// [msg]: packet to be sent out
// return: <0> succeed, <-1> failed
{
    if(!msg)
    {
        return -1;
    }

    if(msg->with_host_info() && msg->has_property(L"Host") && msg->has_property(L"Port"))
    {
        set_serv_host(msg->utf8_property(L"Host"));
        set_serv_port(msg->int_property(L"Port"));
    }

    int sent = send(*msg->data, msg->length);
    if(sent == msg->length) msg->set_done();

    return sent;
}


// ============================================================================
// Send buffer to socket
// ============================================================================
int	h2socket::flush_buffer()
// return: <0> succeed, <-1> failed
{
    if(_send_buff.length() <= 0)
	{
		return 0;
	}

    int total_sent = 0;

    while(_send_buff.length() > 0)
    {
        int sent = ::send(_socket, *_send_buff, _send_buff.length(), 0);
	    if(sent <= 0)
	    {
		    LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Send error %d. (SOCKET=%d)", WSAGetLastError(), _socket);
            closesocket(_socket);
            _socket = INVALID_SOCKET;
		    return total_sent;
	    }

        total_sent += sent;

        _send_buff.pop_front(sent);

	    LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() %d bytes sent. (SOCKET=%d)", sent, _socket);

    }

    return total_sent;
}


// ============================================================================
// Recv data from connect socket
// ============================================================================
int	h2socket::recv()
// return: <-1> failed, <other> bytes received
{
    /*
    if(NULL == *_recv_buff || _recv_buff.length() >= _recv_buff.max_size())
	{
		LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Error recv buffer! (buff=%X, %d/%d)", 
            _recv_buff.pre_buffer(), _recv_buff.pre_length(), _recv_buff.max_size());
		return -1;
	}
    */

	//  check recv buffer
    if(!recv_ready())
    {
        return 0;
    }

    _recv_buff.make_room(H2SOCKET_RECV_BLOCK_SIZE);

    int received = ::recv(_socket, _recv_buff.tail(), _recv_buff.room(), 0);
	if(received <= 0)
	{
		LOG::error(ERR_CODE_SYSTEM, __FUNCTION__"() Socket closed! (SOCKET=%d)", _socket);
        closesocket(_socket);
        _socket = INVALID_SOCKET;
		return -1;
	}

    _recv_buff.extend(received);
    *_recv_buff.tail() = 0;

    LOG::debug(DEBUG_SOCKET | DEBUG_L3, __FUNCTION__"() %d bytes received. (SOCKET=%d, BUFF=%d)", 
        received, _socket, _recv_buff.length());

	return _recv_buff.length();
}


// ============================================================================
// Recv data from connect socket
// ============================================================================
bool h2socket::recv(packet* msg, int wait_ms)
// return: <true> successfully recviced a packet, <false> packet not recevied
{
    recv();

    if(!msg)
    {
        return false;
    }

    timer recv_timer(wait_ms);
    
    while(true)
    {
        int len = msg->decode(*_recv_buff, _recv_buff.length());
        if(len > 0)
        {
            _recv_buff.pop_front(len);
        }

        if(msg->done() || recv_timer.tick())
        {
            break;
        }

        recv_ready(recv_timer.count_down());
        recv();
    }


    return msg->done();
}


// ============================================================================
// FUNCTION connection_ready(): check connection status
// ============================================================================
bool h2socket::connection_ready(int ms_waittime)
{
    if(INVALID_SOCKET == _socket)
    {
        return false;
    }

    fd_set fds_write;
	timeval tv;

	FD_ZERO(&fds_write);
	FD_SET(_socket, &fds_write);
	tv.tv_sec = ms_waittime / 1000;
	tv.tv_usec = (ms_waittime % 1000) * 1000;
	int ret = select(_socket + 1, NULL, &fds_write, NULL, &tv);

    // write ready means connection ready :P
	return (ret > 0 && 0 != FD_ISSET(_socket, &fds_write));
}

// ============================================================================
// FUNCTION recv_ready(): check recv queue
// ============================================================================
bool h2socket::recv_ready(int ms_waittime)
{
    if(INVALID_SOCKET == _socket)
    {
        return false;
    }

	fd_set fds_read;
	timeval tv;

	FD_ZERO(&fds_read);
	FD_SET(_socket, &fds_read);
	tv.tv_sec = ms_waittime / 1000;
	tv.tv_usec = (ms_waittime % 1000) * 1000;
	int ret = select(_socket + 1, &fds_read, NULL, NULL, &tv);

	return (ret > 0 && 0 != FD_ISSET(_socket, &fds_read));
}


// ============================================================================
// FUNCTION http_packet::parse_cookies()
// eocode http packet
// ============================================================================
bool http_packet::set_cookie(const char* cookiestr)
{
    STRING::ansi cookies = cookiestr;

    while(cookies.length() > 0)
    {
        STRING::ansi name, value;

        value = STRING::split(cookies, ";");
        name = STRING::split(value, "=");

        if(name.length())
        {
            cookie[cookie.create(*STRING::unicode(*name))].set(*STRING::utf8(STRING::ansi(*value)));
        }
    }

    return true;
}



// ============================================================================
// FUNCTION http_packet::parse_cookies()
//  update post data string
// ============================================================================
bool http_packet::update_cookies()
{
    STRING::utf8 cookies(4096);
    for(int i = 0; i < cookie.length(); ++i)
    {
        if(i) STRING::cat(cookies, "; ");
        STRING::cat(cookies, *STRING::utf8(cookie.name(i)));
        STRING::cat(cookies, "=");
        STRING::cat(cookies, cookie[i].utf8());
    }

    set_property(L"Cookie", *cookies);
    return true;
}


// ============================================================================
// FUNCTION http_packet::encode()
// eocode http packet
// ============================================================================
bool http_packet::encode()
{
    if(!has_property(L"URL"))
    {
        return false;
    }

    // Parse URL first
    STRING::ansi location(utf8_property(L"URL"));
    STRING::ansi host, port, protocol;

    // "http://www.domain.com:80/location" => "www.domain.com:80/location"
    if(!location.is_null() && STRING::find(location, 0, "://") >= 0) protocol = STRING::split(location, "://");
    // "www.domain.com:80/location" => port = "www.domain.com:80", location = "location"
    if(!location.is_null()) port = STRING::split(location, "/");
    // "www.domain.com:80" => host="www.domain.com", port = "80"
    if(!port.is_null()) host = STRING::split(port, ":");

    if(port.length() == 0)
    {
        port = "80";
    }

    set_property(L"Protocol", *protocol);
    set_property(L"Host", *host);
    set_property(L"Port", atoi(*port));
    set_property(L"Location", *(STRING::ansi("/") + location));

    data.resize(4096);

    update_cookies();
    STRING::ansi method = utf8_property(L"Method");
    STRING::ansi cookie = utf8_property(L"Cookie");
    STRING::ansi content = utf8_property(L"Content");

    // GET /location HTTP 1.1
    STRING::cat(data, *method); STRING::cat(data, " "); STRING::cat(data, utf8_property(L"Location")); 
    STRING::cat(data, " "); STRING::cat(data, utf8_property(L"Version")); STRING::cat(data, "\r\n");

    if(has_property(L"x-requested-with"))
    {
        STRING::cat(data, "x-requested-with: "); STRING::cat(data, utf8_property(L"x-requested-with")); STRING::cat(data, "\r\n");
    }

    STRING::cat(data, "Accept-Language: "); STRING::cat(data, utf8_property(L"Accept-Language")); STRING::cat(data, "\r\n");
    STRING::cat(data, "Accept: "); STRING::cat(data, utf8_property(L"Accept")); STRING::cat(data, "\r\n");

    if(has_property(L"Referer"))
    {
        STRING::cat(data, "Referer: "); STRING::cat(data, utf8_property(L"Referer")); STRING::cat(data, "\r\n");
    }

    STRING::cat(data, "Accept-Encoding: "); STRING::cat(data, utf8_property(L"Accept-Encoding")); STRING::cat(data, "\r\n");
    STRING::cat(data, "User-Agent: "); STRING::cat(data, utf8_property(L"User-Agent")); STRING::cat(data, "\r\n");
    STRING::cat(data, "Host: "); STRING::cat(data, utf8_property(L"Host")); STRING::cat(data, "\r\n");

    if(method == "POST")
    {
        char content_len[256];
        sprintf_s(content_len, 256, "Content-Length: %d\r\n", content.length());
        STRING::cat(data, content_len);
        STRING::cat(data, "Connection: "); STRING::cat(data, utf8_property(L"Connection")); STRING::cat(data, "\r\n");
        STRING::cat(data, "Cache-Control: no-cache\r\n");
    }
    else
    {
        STRING::cat(data, "Connection: "); STRING::cat(data, utf8_property(L"Connection")); STRING::cat(data, "\r\n");
    }

    if(cookie.length() > 0)
    {
        STRING::cat(data, "Cookie: "); STRING::cat(data, *cookie); STRING::cat(data, "\r\n");
    }
    
    STRING::cat(data, "\r\n");

    if(method == "POST" && content.length() > 0)
    {
        STRING::cat(data, *content);
        STRING::cat(data, "\r\n");
    }

    length = data.length();
    set_property(L"Request", *data);
    return true;
}



// ============================================================================
// FUNCTION http_packet::decode()
// decode http response packet
// ============================================================================
int http_packet::decode(char* buf, int len)
{
    if(!buf || !len)
    {
        return 0;
    }

    char *read = buf;

    ///////////////////////////////////////////////////////////////////////////
    // Parse HTTP Response Title
    // e.g. "HTTP/1.1 200 OK"
    if(HTTP_STATE_TITLE == _state)
    {
        // get first line
        char cr[2][3] = {"\r\n", "\n"};
        char *p = NULL;
        int crlen = 0;
        for(int i = 0; i < 2; ++i) if(p = strstr(read, cr[i])) { crlen = strlen(cr[i]); break; }
        if(NULL == p) return read - buf;

        data.resize(p - read);
        memcpy(*data, buf, p - read);
        read = p + crlen;

        // parse title "HTTP/1.1 200 OK"
        STRING::ansi version(256), info(256);
        int code;

        if(sscanf_s(*data, "%s %d %s", *version, version.size(), &code, *info, info.size()) != 3)
        {
            LOG::debug(DEBUG_HTTP | DEBUG_L3, __FUNCTION__ "() invalid HTTP response header: '%s'", *data);
        }

        set_property(L"Version", *version);
        set_property(L"Code", code);
        set_property(L"Info", *info);
        STRING::cat(data, "\r\n");
        _state = HTTP_STATE_HEADER;
    }


    ///////////////////////////////////////////////////////////////////////////
    // Parse HTTP Response Header
    if(HTTP_STATE_HEADER == _state)
    {
        // find continuious two <CR><LF>
        char cr[2][5] = {"\r\n\r\n", "\n\n"};
        char *p = NULL;
        int crlen= 0;
        for(int i = 0; i < 2; ++i) if(p = strstr(read, cr[i])) { crlen = strlen(cr[i]); break; }
        if(NULL == p) return read - buf;

        // read header
        STRING::ansi header(p - read);
        memcpy(*header, read, p - read);
        STRING::cat(data, read, (p - read) + crlen);
        read = p + crlen;

        // parse header
        while(header.length() > 0)
        {
            STRING::ansi name, value;
            if(crlen == 4) value = STRING::split(header, "\r\n");
            else value = STRING::split(header, "\n");
            name = STRING::split(value, ":");
            if(name.length() > 0 && *value)
            {
                set_property(*STRING::unicode(*name), *value);
            }
        }

        if(has_property(L"Transfer-Encoding") && 0 == _stricmp(utf8_property(L"Transfer-Encoding"), "chunked"))
        {
            _state = HTTP_STATE_CHUNK;
            remove_property(L"Content");
        }
        else if(has_property(L"Content-Length"))
        {
            _state = HTTP_STATE_CONTENT;
            remove_property(L"Content");
        }
        else
        {
            set_property(L"Code", 600);
            set_property(L"Info", "Missed Content-Length in unchunked stream.");
            _state = HTTP_STATE_COMPLETE;
        }
    }


    ///////////////////////////////////////////////////////////////////////////
    // Parse HTTP Response Content
    if(HTTP_STATE_CONTENT == _state)
    {
        int length = int_property(L"Content-Length");
        if(length > len - (read - buf))
        {
            return read - buf;
        }

        STRING::ansi content(length);
        if(length)
        {
            memcpy(*content, read, length);
        }

        read += length;
        STRING::cat(data, *content);
        set_property(L"Content", *content);
        _state = HTTP_STATE_COMPLETE;
    }


    ///////////////////////////////////////////////////////////////////////////
    // Parse HTTP Response Chunked Stream
    if(HTTP_STATE_CHUNK == _state)
    {
        while(true)
        {
            // get first line
            char cr[2][3] = {"\r\n", "\n"};
            char *p = NULL;
            int crlen = 0;
            for(int i = 0; i < 2; ++i) if(p = strstr(read, cr[i])) { crlen = strlen(cr[i]); break; }
            if(NULL == p) return read - buf;
        
            // parse chunk size
            STRING::ansi chunksize(p - read);
            memcpy(*chunksize, read, p - read);
            p += crlen;

            STRING::trim(chunksize);

            // continuious two cr means chunk end
            if(chunksize.length() == 0)
            {
                STRING::cat(data, read, p - read);
                read = p;
                continue;
            }

            int size = (int)strtol(*chunksize, NULL, 16);
            if(size > len - (p - buf))
            {
                return read - buf;
            }

            STRING::cat(data, read, p - read + size);
            read = p + size;

            if(size == 0)
            {
                _state = HTTP_STATE_COMPLETE;
                break;
            }

            STRING::utf8 content = utf8_property(L"Content");
            STRING::ansi tmpstr(size); memcpy(*tmpstr, p, size);
            STRING::utf8 tmputf8 = tmpstr;
            STRING::cat(content, *tmputf8);
            set_property(L"Content", *content);
        }
    }

    set_property(L"Response", *STRING::utf8(STRING::ansi(*data)));
    length = data.length();
    return read - buf;
}



bool uap_packet::encode()
{
    if(!has_property(L"Command_ID") || !has_property(L"Command_Status") || !has_property(L"Sender_ID")|| !has_property(L"Receiver_ID"))
    {
        return false;
    }

    int Command_ID = int_property(L"Command_ID");
    int Command_Status = int_property(L"Command_Status");
    int Sender_ID = int_property(L"Sender_ID");
    int Receiver_ID = int_property(L"Receiver_ID");
    int Command_Length = 20;

    data.resize(Command_Length);
    char *msg = *data;

    int offset = 4;
    *(int*)msg = htonl(Command_Length);
    *(int*)&msg[offset] = htonl(Command_ID); offset += 4; 
    *(int*)&msg[offset] = htonl(Command_Status); offset += 4; 
    *(int*)&msg[offset] = htonl(Sender_ID); offset += 4; 
    *(int*)&msg[offset] = htonl(Receiver_ID); offset += 4; 


    if(Command_ID == 0x00000065) // BIND
    {
        if(!has_property(L"System_ID") || !has_property(L"Password") || !has_property(L"System_Type")|| !has_property(L"Interface_Version"))
        {
            return false;
        }

        STRING::ansi System_ID = utf8_property(L"System_ID");
        STRING::ansi Password = utf8_property(L"Password");
        STRING::ansi System_Type = utf8_property(L"System_Type");
        int Interface_Version = int_property(L"Interface_Version");

        Command_Length = 20 + 11 + 9 + 13 + 4;
        data.resize(Command_Length);

        *(int*)msg = htonl(Command_Length);
        System_ID.resize(11); memcpy(&msg[offset], *System_ID, 11); offset += 11;
        Password.resize(9); memcpy(&msg[offset], *Password, 9); offset += 9;
        System_Type.resize(13); memcpy(&msg[offset], *System_Type, 13); offset += 13;
        *(int*)&msg[offset] = htonl(Interface_Version); offset += 4; 
    }

    if(Command_ID == 0x0000006F || Command_ID == 0x00000070 || Command_ID == 0x00000071) 
        // BEGIN                        CONTINUE                    END
    {
        int Ussd_Version = int_property(L"Ussd_Version");
        int Ussd_Op_Type = int_property(L"Ussd_Op_Type");
        STRING::ansi MsIsdn = utf8_property(L"MsIsdn");
        STRING::ansi Service_Code = utf8_property(L"Service_Code");
        int Code_Scheme = int_property(L"Code_Scheme");
        STRING::ansi Ussd_Content = utf8_property(L"Ussd_Content");

        int content_len = hex_to_bin(Ussd_Content);
        Command_Length = 20 + 3 + 21 + 21 + content_len;
        data.resize(Command_Length);

        *(int*)msg = htonl(Command_Length);
        msg[offset] = (char)Ussd_Version; offset ++; 
        msg[offset] = (char)Ussd_Op_Type; offset ++; 
        MsIsdn.resize(21); memcpy(&msg[offset], *MsIsdn, 21); offset += 21;
        Service_Code.resize(21); memcpy(&msg[offset], *Service_Code, 21); offset += 21;
        msg[offset] = (char)Code_Scheme; offset ++; 
        if(content_len) { memcpy(&msg[offset], *Ussd_Content, content_len); offset += content_len; }
    }

    length = Command_Length;

    return true;
}


// ============================================================================
// FUNCTION http_packet::decode()
// decode http response packet
// ============================================================================
int uap_packet::decode(char* buf, int len)
{
    if(len < 20)
    {
        return 0;
    }

    // Parse header
    int offset = 0;
    int Command_Length = ntohl(*(int*)&buf[offset]); offset += 4;
    int Command_ID = ntohl(*(int*)&buf[offset]); offset += 4;
    int Command_Status = ntohl(*(int*)&buf[offset]); offset += 4;
    int Sender_ID = ntohl(*(int*)&buf[offset]); offset += 4;
    int Receiver_ID = ntohl(*(int*)&buf[offset]); offset += 4;

    if(Command_Length > len)
    {
        return 0;
    }

    length = Command_Length;
    data.resize(length);
    memcpy(*data, buf, length);

    set_property(L"Command_Length", Command_Length);
    set_property(L"Command_ID", Command_ID);
    set_property(L"Command_Status", Command_Status);
    set_property(L"Sender_ID", Sender_ID);
    set_property(L"Receiver_ID", Receiver_ID);

    if(Command_ID == 0x00000067) // BIND_RESP
    {
        int len = min(11, Command_Length - 20);
        if(len > 0)
        {
            STRING::ansi System_ID(len);
            memcpy(*System_ID, &buf[offset], len);
            set_property(L"System_ID", *System_ID);
        }
        else
        {
            set_property(L"System_ID", "");
        }
    }

    if(Command_ID == 0x0000006F) // BEGIN
    {
        set_property(L"Ussd_Version", (int)(unsigned char)buf[offset]); offset ++;
        set_property(L"Ussd_Op_Type", (int)(unsigned char)buf[offset]); offset ++;
        set_property(L"MsIsdn", &buf[offset]); offset += strlen(&buf[offset]) + 1;
        set_property(L"Service_Code", &buf[offset]); offset += strlen(&buf[offset]) + 1;
        set_property(L"Code_Scheme", (int)(unsigned char)buf[offset]); offset ++;
        set_property(L"Ussd_Content", &buf[offset]);
    }

    _state = true;
    return Command_Length;
}



bool ussd_packet::encode()
{
    if(!has_property(L"Command_ID") || !has_property(L"Sequence_ID"))
    {
        return false;
    }

    int Command_Type = int_property(L"Command_Type");
    int Command_ID = int_property(L"Command_ID");
    int Sequence_ID = int_property(L"Sequence_ID");
    int Command_Status = int_property(L"Command_Status");
    int Command_Length = 8;

    data.resize(Command_Length);

    int offset = 2;
    *(unsigned short*)*data = htons(Command_Length);
    data[offset] = (unsigned char)Command_Type; offset++; 
    data[offset] = (unsigned char)Command_ID; offset++; 
    *(int*)&data[offset] = htonl(Sequence_ID); offset += 4; 

    if(Command_ID == 0x00000010 && Command_Type == 0) // BIND
    {
        if(!has_property(L"MsIsdn"))
        {
            return false;
        }

        STRING::ansi MsIsdn= utf8_property(L"MsIsdn");

        Command_Length = 8 + 14;

        *(unsigned short*)*data = htons(Command_Length);
        MsIsdn.resize(14); memcpy(&data[offset], *MsIsdn, 14); offset += 14;
    }

    if(Command_ID == 0x00000001 && Command_Type == 0)    // BEGIN
    {
        STRING::ansi Content = utf8_property(L"Content");
        int content_length = Content.length();

        Command_Length = 8 + content_length;
        data.resize(Command_Length);

        *(unsigned short*)*data = htons(Command_Length);
        if(content_length)memcpy(&data[offset], *Content, content_length); offset += content_length;
    }

    if(Command_ID == 0x00000002 && Command_Type == 0)    // CONTINUE
    {
        STRING::ansi Content = utf8_property(L"Content");
        int Session_ID = int_property(L"Session_ID");

        int content_length = Content.length();

        Command_Length = 8 + 4 + content_length;
        data.resize(Command_Length);

        *(unsigned short*)*data = htons(Command_Length);
        *(int*)&data[offset] = htonl(Session_ID); offset += 4; 
        if(content_length) memcpy(&data[offset], *Content, content_length); offset += content_length;
    }

    if(Command_Type == 128)
    {
        Command_Length = 9;
        data.resize(Command_Length);
        *(unsigned short*)*data = htons(Command_Length);
        data[offset] = (unsigned char)Command_Status; offset ++;
    }

    length = Command_Length;

    return true;
}


// ============================================================================
// FUNCTION ussd_packet::decode()
// decode ussd response packet
// ============================================================================
int ussd_packet::decode(char* buf, int len)
{
    if(len < 8)
    {
        return 0;
    }

    // Parse header
    int offset = 0;
    int Command_Length = (int)ntohs(*(short*)&buf[offset]); offset += 2;
    int Command_Type = *(unsigned char*)&buf[offset]; offset ++;
    int Command_ID = *(unsigned char*)&buf[offset]; offset ++;
    int Sequence_ID = ntohl(*(int*)&buf[offset]); offset += 4;

    if(Command_Length > len)
    {
        return 0;
    }

    length = Command_Length;
    data.resize(length);
    memcpy(*data, buf, length);

    set_property(L"Command_Length", Command_Length);
    set_property(L"Command_ID", Command_ID);
    set_property(L"Command_Type", Command_Type);
    set_property(L"Sequence_ID", Sequence_ID);

    if(Command_ID == 0x01) // BEGIN_RESP
    {
        if(Command_Length >= 9)
        {
            set_property(L"Command_Status", (int)*(unsigned char*)&buf[offset]); offset++;
        }
        if(Command_Length >= 13)
        {
            set_property(L"Session_ID", ntohl(*(int*)&buf[offset])); offset++;
        }        
    }

    if(Command_ID == 0x05 || Command_ID == 0x07) // DISPLAY
    {
        STRING::ansi msg(Command_Length - 8);
        memcpy(*msg, &buf[offset], Command_Length - 8);
        set_property(L"Content", *msg); offset += Command_Length - 8;
    }

    _state = true;
    return Command_Length;
}







bool smpp_packet::encode()
{
    if(!has_property(L"Command_ID") || !has_property(L"Sequence_ID"))
    {
        return false;
    }

    int Command_Status = int_property(L"Command_Status");
    int Command_ID = int_property(L"Command_ID");
    int Command_Type = has_property(L"Command_Type") ? int_property(L"Command_Type") : 0;
    int Sequence_ID = int_property(L"Sequence_ID");
    if(Command_Type) Command_ID = Command_ID | 0x80000000;

    data.resize(16);

    int offset = 4;
    *(int*)&data[offset] = htonl(Command_ID); offset += 4; 
    *(int*)&data[offset] = htonl(Command_Status); offset += 4; 
    *(int*)&data[offset] = htonl(Sequence_ID); offset += 4; 


    STRING::utf8 tmpstr;
    int len;

    switch(Command_ID)
    {
    case ESME_BNDRCV:
    case ESME_BNDTRN:
        tmpstr = utf8_property(L"System_ID"); tmpstr.resize(15); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        tmpstr = utf8_property(L"Password"); tmpstr.resize(8); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        tmpstr = utf8_property(L"System_Type"); tmpstr.resize(12); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Interface_Version"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Addr_TON"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Addr_NPI"); offset++;
        tmpstr = utf8_property(L"Address_Range"); tmpstr.resize(40); memcpy(&data[offset], *tmpstr, tmpstr.length() + 1); offset += tmpstr.length() + 1;
        break;
    case ESME_BNDRCV_RESP:
    case ESME_BNDTRN_RESP:
        tmpstr = utf8_property(L"System_ID"); tmpstr.resize(15); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        break;
    case ESME_SUB_SM:
    case SMSC_DELIVER_SM:
        tmpstr = utf8_property(L"Service_Type"); tmpstr.resize(5); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Source_Addr_TON"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Source_Addr_NPI"); offset++;
        tmpstr = utf8_property(L"Source_Addr"); tmpstr.resize(20); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Dest_Addr_TON"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Dest_Addr_NPI"); offset++;
        tmpstr = utf8_property(L"Destination_Addr"); tmpstr.resize(20); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"ESM_Class"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Protocol_ID"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Priority_Flag"); offset++;
        tmpstr = utf8_property(L"Schedule_Delivery_Time"); tmpstr.resize(16); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        tmpstr = utf8_property(L"Validity_Period"); tmpstr.resize(16); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Registered_Delivery_Flag"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Replace_If_Present_Flag"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"Data_Coding"); offset++;
        data.resize(offset + 1); data[offset] = (unsigned char)int_property(L"SM_Default_Msg_ID"); offset++;
        tmpstr = utf8_property(L"Short_Message"); tmpstr.resize(159); len = tmpstr.length(); 
        data.resize(offset + 1); data[offset] = (unsigned char)len; offset++;
        data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        break;
    case ESME_SUB_SM_RESP:
    case SMSC_DELIVER_SM_RESP:
        tmpstr = utf8_property(L"Message_ID"); tmpstr.resize(8); len = tmpstr.length() + 1; data.resize(offset + len); memcpy(&data[offset], *tmpstr, len); offset += len;
        break;
    }


    length = offset;
    set_property(L"Command_Length", length);
    *(int*)*data = htonl(length);

    return true;
}


int smpp_packet::decode(char* buf, int buflen)
{
    if(buflen < 16)
    {
        return 0;
    }

    // Parse header
    int offset = 0;
    int Command_Length = (int)ntohl(*(int*)&buf[offset]); offset += 4;
    int Command_ID = (int)ntohl(*(int*)&buf[offset]); offset += 4;
    int Command_Status = (int)ntohl(*(int*)&buf[offset]); offset += 4;
    int Sequence_ID = (int)ntohl(*(int*)&buf[offset]); offset += 4;
    int Command_Type = (Command_ID & 0x80000000) ? 1 : 0;

    if(Command_Length > buflen)
    {
        return 0;
    }

    length = Command_Length;
    data.resize(length);
    memcpy(*data, buf, length);

    set_property(L"Command_Length", Command_Length);
    set_property(L"Command_ID", Command_ID & 0xff);
    set_property(L"Command_Type", Command_Type);
    set_property(L"Command_Status", Command_Status);
    set_property(L"Sequence_ID", Sequence_ID);

    STRING::utf8 tmpstr(256);
    int len;
    char data_coding = 0;

    switch(Command_ID)
    {
    case ESME_BNDRCV:
    case ESME_BNDTRN:
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"System_ID", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Password", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"System_Type", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset <= Command_Length){ set_property(L"Interface_Version", (unsigned char)buf[offset]); offset ++; }
        if(offset <= Command_Length){ set_property(L"Addr_TON", (unsigned char)buf[offset]); offset ++; }
        if(offset <= Command_Length){ set_property(L"Addr_NPI", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Address_Range", *tmpstr); offset += tmpstr.length() + 1; }
        break;
    case ESME_BNDRCV_RESP:
    case ESME_BNDTRN_RESP:
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"System_ID", *tmpstr); offset += tmpstr.length() + 1; }
        break;
    case ESME_SUB_SM:
    case SMSC_DELIVER_SM:
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Service_Type", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { set_property(L"Source_Addr_TON", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"Source_Addr_NPI", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Source_Addr", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { set_property(L"Dest_Addr_TON", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"Dest_Addr_NPI", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Destination_Addr", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { set_property(L"ESM_Class", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"Protocol_ID", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"Priority_Flag", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Schedule_Delivery_Time", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Validity_Period", *tmpstr); offset += tmpstr.length() + 1; }
        if(offset < Command_Length) { set_property(L"Registered_Delivery_Flag", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"Replace_If_Present_Flag", (unsigned char)buf[offset]); offset ++; }
        data_coding = buf[offset];
        if(offset < Command_Length) { set_property(L"Data_Coding", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"SM_Default_Msg_ID", (unsigned char)buf[offset]); offset ++; }
        if(offset < Command_Length) { set_property(L"SM_Length", (int)(unsigned char)buf[offset]); offset ++; }
        len = int_property(L"SM_Length"); len = min(Command_Length - offset, len);
        if((data_coding & 0x0c) == 0x08) // unicode
        {
            STRING::unicode utmp(len/2);
            short* p = (short*)(buf + offset);
            int i;
            for(i = 0; i < len / 2; ++ i)
            {
                utmp[i] = (wchar_t)ntohs((short)*(p++));
            }
            tmpstr = utmp;
            utmp = (STRING::utf8&)STRING::utf8(*tmpstr);
        }
        else if(len >= 0) { tmpstr = buf + offset; }
        set_property(L"Short_Message", *tmpstr); offset += len;
        break;
    case ESME_SUB_SM_RESP:
    case SMSC_DELIVER_SM_RESP:
        if(offset < Command_Length) { tmpstr = buf + offset; set_property(L"Message_ID", *tmpstr); offset += tmpstr.length() + 1; }
        break;
    }

    _state = true;
    return Command_Length;
}
